/**
 * @author Nicolas Zimerman (zimerman@usi.ch)
 * @author Isabella Nake (isabella.nake@usi.ch)
 * @author Nicolas Lusa (lusan@usi.ch)
 * @author Nicolas Latorre (nicolas.latorre@usi.ch)
 */
package ch.usi.inf.pf2.circuit;

import java.util.ArrayList;
import java.util.HashMap;

import ch.usi.inf.pf2.time.ComputeEvent;
import ch.usi.inf.pf2.time.Event;
import ch.usi.inf.pf2.time.Eventable;
import ch.usi.inf.pf2.time.PassValueToOutputPinEvent;
import ch.usi.inf.pf2.time.Schedule;

public abstract class Gate extends Component implements Eventable {
	private static final long serialVersionUID = -1152928795187712664L;
	protected OutputPin outputPin;
	protected int delay;
	protected Value[] values;
	protected HashMap<Integer, Value> valuesInputPin1;
	protected HashMap<Integer, Value> valuesInputPin2;
	protected Value valueAtExit;
	protected InputPinForGate inputPin1;
	protected InputPinForGate inputPin2;
	protected Wire wireInputPin1;
	protected Wire wireInputPin2;
	protected ArrayList<Wire> wiresOutputPin;
	
	
	
	public Gate() {
		this.outputPin = new OutputPin();
		this.delay = 1;
		this.values = new Value[2];
		this.values[0] = Value.UNKNOWN;
		this.values[1] = Value.UNKNOWN;
		valuesInputPin1 = new HashMap<Integer, Value>();
		valuesInputPin2 = new HashMap<Integer, Value>();
		this.valueAtExit = Value.UNKNOWN;
		this.inputPin1 = null;
		this.inputPin2 = null;
		this.wireInputPin1 = null;
		this.wireInputPin2 = null;
		this.wiresOutputPin = new ArrayList<Wire>();
	}
	
	//MODEL PART
	
	public void setOutputPin(final OutputPin outputPin){
		this.outputPin = outputPin;
	}
	
	public void setWireInputPin1(final Wire wire) {
		this.wireInputPin1 = wire;
		inputPin1 = new InputPinForGate(this);
		wire.setInputPin(inputPin1);
	}
	public void setWireInputPin2(final Wire wire) {
		this.wireInputPin2 = wire;
		inputPin2 = new InputPinForGate(this);
		wire.setInputPin(inputPin2);
	}
	public void setWireOutputPin(final Wire wire) {
		this.wiresOutputPin.add(wire);
		outputPin.addWire(wire);
		wire.setOutputPin(outputPin);
	}
	
	public void setDelay(int delay){
		this.delay = delay;
	}
	
	public int getDelay(){
		return this.delay;
	}
	
	public void setValue(final Value value, final InputPinForGate inputPin) {
		/*
		if(this.inputPin1 == null) {
			this.inputPin1 = inputPin;
		} else if(!inputPin.equals(this.inputPin1) && this.inputPin2 == null) {
			this.inputPin2 = inputPin;
		}
		*/
		
		if(inputPin.equals(this.inputPin1)) {
			valuesInputPin1.put(Schedule.getStep(), value);
		} else if(inputPin.equals(inputPin2)) {
			valuesInputPin2.put(Schedule.getStep(), value);
		}
	}

	public void execute(Event ev) {
		if(ev instanceof ComputeEvent) {
			this.compute();
		} else {
			this.passValueToOutputPin();
		}
	}
	
	public abstract void compute();
	
	public void passValueToOutputPin() {
		outputPin.setValue(this.valueAtExit);
	}
	
	public Value getValueAtExit() {
		return valueAtExit;
	}
	
	protected void createEvent() {
		new PassValueToOutputPinEvent(valueAtExit, this, Schedule.getStep());
	}
	
	@Override
	public Wire[] getWires() {
		Wire[] w = new Wire[wiresOutputPin.size() + 2];
		w[0] = wireInputPin1;
		w[1] = wireInputPin2;
		for(int i = 0; i < wiresOutputPin.size(); i++) {
			w[i+2] = wiresOutputPin.get(i);
		}
		return w;
	}
	
	public InputPin getInputPin1() {
		return inputPin1;
	}

	public InputPin getInputPin2() {
		return inputPin2;
	}
	
	//GUI PART
	
	public abstract int getXInputPin1();
	public abstract int getYInputPin1();
	public abstract int getXInputPin2();
	public abstract int getYInputPin2();
	public abstract int getXOutputPin();
	public abstract int getYOutputPin();
	
	public void moveWire(final int deltaX, final int deltaY) {
		if(wireInputPin1 != null) {
			wireInputPin1.moveXY2(deltaX, deltaY);
		}
		if(wireInputPin2 != null) {
			wireInputPin2.moveXY2(deltaX, deltaY);
		}
		if(wiresOutputPin != null) {
			for(Wire wire: wiresOutputPin) {
				wire.moveXY1(deltaX, deltaY);
			}
		}
	}
	
	public String getColor() {
		if(valueAtExit == Value.TRUE) {
			return "green";
		} else if(valueAtExit == Value.FALSE) {
			return "blue";
		} else {
			return "black";
		}
	}

	public void removeInputPin(InputPinForGate inputPinForGate) {
		if(inputPinForGate.equals(inputPin1)) {
			inputPin1 = null;
		} else if(inputPinForGate.equals(inputPin2)) {
			inputPin2 = null;
		}
		
	}


}
